7. 列表与容器 Lists & Containers
一些用于列表的操作符:
<element> in <container>判断某个元素是否存在于某个容器中。它只能判断单个元素而无法判断关于子序列的问题。for <element> in <container>用于迭代容器中的每个元素,并将其赋给element元素。其详细的执行过程如下:- 计算表达式
<container>的值,其必须返回一个可迭代的容器。 - 按顺序,对容器内的每一个元素:
- 将该元素绑定到
<element> - 执行循环主体部分
- 将该元素绑定到
- 计算表达式
范围(Ranges) 是另一种不同于列表的序列,其用于表示连续的整数。其形如range(begin = 0, end, step = 1)。这会给出一个所含区间
列表推导(List Comprehension) 是 Python 中一个十分强大的功能。其可以使用原有的列表通过十分简洁的写法得到新的列表,其格式为:
[表达式 for 变量 in 列表]
[out_exp_res for out_exp in input_list]
or
[表达式 for 变量 in 列表 if 条件]
[out_exp_res for out_exp in input_list if condition]
>>> [x+1 for x in range(8)]
[1, 2, 3, 4, 5, 6, 7, 8]
>>> [x for x in range(20) if x % 2 == 0]
[0, 2, 4, 6, 8, 10, 12, 14, 16, 18]
>>> names = ['Bob','Tom','alice','Jerry','Wendy','Smith']
>>> [name.upper() for name in names if len(name)>3]
['ALICE', 'JERRY', 'WENDY', 'SMITH']
列表由于具有闭包的性质,即可以通过将一个元素放入列表的方法将该列表放入其它列表中以形成层次结构,我们因此使用 盒子与指针表示法(Box-and-Pointer-Notation) 来表示一个列表。即我们使用一个盒子表示列表,每个盒子内部要么是一个值,要么是一个指向其它复合值(例如函数、列表)的指针,如下图所示:
/Pasted%20image%2020250302110449.png)
切片(Slicing) 是一种能对列表和范围等序列执行的操作,以得到新的列表。其形如
seq[start = 0:end = len(seq):step = 1]
并返回一个新的列表,其包括原列表 seq 中索引在
序列的加法同样会返回一个新的序列,其由两个被操作序列的所有元素组成。
list() 函数与 [] 字面量语法同样可以创建新的列表。
上述所有操作(切片、加法、list()、[])均为 浅拷贝。即,生成的新列表只是包含了对原有元素的引用(即指向相同的内存)。浅拷贝具有以下两点特征:
- 顶层元素独立:直接修改新列表的顶层结构(如增删、替换元素),不会影响原列表。
- 嵌套元素共享:如果元素本身是可变对象(如列表、字典),修改这些嵌套对象的内容会影响所有引用它们的列表。
>>> s = [1,2,3]
>>> t = [4,5]
>>> a = s + [t]
>>> b = s + t
>>> a
[1, 2, 3, [4, 5]]
>>> b
[1, 2, 3, 4, 5]
>>> a[3][1] = 100
>>> a
[1, 2, 3, [4, 100]]
>>> t
[4, 100]
>>> b
[1, 2, 3, 4, 5]
解释:[t] 首先创建了一个匿名列表,其 第一个元素是指向列表 t 的引用。随后,通过将该列表与 s 相加得到一个新列表,并将其绑定到变量名 a。表达式 a[3] 与 t 指向了内存中的同一片区域。因此当执行 a[3][1] = 100 时,t 的值也会改变。然而对 b 而言,其得到的列表包含 5 个整数,整数是不可变元素(或者可以理解为 5 个指向这些不可变整数的引用)。因此对 t 的修改没有影响 b。环境图如下所示:
/Pasted%20image%2020250322100737.png)
>>> a = [1, 2, 3]
>>> b = list(a)
>>> b.append(4)
>>> a
[1, 2, 3]
>>> b
[1, 2, 3, 4]
解释:仔细理解 list() 到底复制的是什么的引用。当第二行为 list(a) 时,复制的是 三个指向整数的引用;假设将其改为 list([a]),复制的则是一个 指向一个列表 [1,2,3] 的引用。二者的环境图分别如下:
/Pasted%20image%2020250322101728.png)
/Pasted%20image%2020250322101706.png)
>>> lst = [[]] * 5
>>> lst[1].append(1)
>>> lst
[[1], [1], [1], [1], [1]]
了解了浅拷贝的特征后,我们可以构建一些奇特的列表:
/Pasted%20image%2020250322102453.png)
列表有大量内置方法:
append(obj)将表达式obj的值插入到序列末尾。extend(iterable)将可迭代对象iterable的所有对象添加到序列末尾。pop()将列表的最后一个元素从列表中移除并返回该元素。count(val)返回列表中等于val的元素个数。
需要注意的是 append 方法直接新开一个元素空间并存放指向 obj 的引用。而 extend 方法是将可迭代对象中的所有元素依次添加到序列末尾(可以理解为这之中隐含了一层解包)。
>>> a = [1,2,3]
>>> b = [4,5]
>>> a.append(b)
>>> a
[1,2,3,[4,5]]
>>> a.extend(b)
>>> a
[1,2,3,[4,5],4,5]
Python 有许多内置函数接受一个序列并返回一个值:
sum(iterable[,start = 0]) -> value将该非字符串的可迭代容器中的每个值加到start上,并返回start的值。不仅仅是纯数值序列可以求和,一个以列表为元素的列表也可以求和,结果为各子列表拼接在一起后的列表。当然求和的数据类型必须兼容。max(iterable[,key = func]) -> value根据键函数key对可迭代对象中每个元素的返回值,返回该序列中的“最大”元素。键函数默认为恒等函数,即lambda x: x。与之对应的函数为min。all(iterable) -> bool判断列表中的每个元素是否为True。当对该可迭代对象为空或对每个值应用bool()函数,结果都为True时,结果为True,否则为False。与之对应的函数为any。
字符串是一种特殊的序列,用于表示文本数据。其与序列略有不同。例如,我们可以使用 in 来判断某个字符串是否为某个字符串的子串。
Python 有内置函数 repr 与 str,它们都将某个对象或表达式转化为字符串输出,但规则略有不同:
repr的工作方式类似于在 Python 交互式解释器 中输入一个对象,其结果等于 Python 对表达式的标准字符串表示,如同在 Python 交互式解释器中输入一个表达式得到的结果。对于某些过于复杂的对象而言(例如函数),repr只会输出一个用尖括号<>包起来的代理字符串表示。对于不是使用尖括号括起来的内容,有表达式eval(repr(obj)) == obj成立,也即repr函数的输出可以通过eval函数重建。- 而
str的工作方式类似于print函数。它会计算表达式的值并将其输出。
>>> from fractions import Fraction
>>> half = Fraction(1,2)
>>> repr(half)
'Fraction(1, 2)'
>>> half
Fraction(1, 2)
>>> str(half)
'1/2'
>>> print(half)
1/2
>>> eval(repr(half))
Fraction(1, 2)
字符串插值(String Interpolation) 是指将一些表达式的值插入字符串中的技术。Python 首选的字符串插值方式为在某个字符串前添加 f 符号。使用 f 符号修饰的字符串中,所有使用大括号括起来的内容都将被视为一个表达式,该表达式会在当前环境中求值,将值通过 str() 处理后插入大括号所在的地方。
字典(Dictionary) 是 Python 中用于存储 键值对 的容器,同时也是存储 键 的容器。通过 list 将字典转换为列表时,得到的是包含所有键的列表。若要获得包含所有值的列表,需要进行 list(dict.values())。字典要求键必须是 可哈希(Hashable) 的,即该对象的哈希值在其生命周期内从不改变。整数、浮点数、字符串、元组 都是可哈希的,而 列表、字典 是不可哈希的。
字典同样存在字典推导,其形如:
{<键表达式>: <值表达式> for <变量名> in <可迭代对象> if <过滤条件表达式>}